package ru.susu.algebra.centralunits.alternating;

import java.io.PrintStream;
import java.math.BigInteger;
import java.util.Date;
import java.util.List;

import org.apache.log4j.Logger;

import ru.susu.algebra.centralunits.CentralUnitsOptions;
import ru.susu.algebra.centralunits.alternating.tex.HeadAnTexGenerator;
import ru.susu.algebra.centralunits.alternating.tex.LocalCaseTexGenerator;
import ru.susu.algebra.centralunits.alternating.tex.TitleTexGenerator;
import ru.susu.algebra.chartable.constructor.AlternatingCharTableUtil;
import ru.susu.algebra.chartable.constructor.AlternatingGroupCharTableConstructor;
import ru.susu.algebra.field.quadratic.QuadraticFieldGeneralForm;
import ru.susu.algebra.field.quadratic.QuadraticInteger;
import ru.susu.algebra.field.quadratic.QuadraticIntegerExtractor;
import ru.susu.algebra.jtex.ITexDocument;
import ru.susu.algebra.jtex.TexDocument;
import ru.susu.algebra.matrix.Matrix;
import ru.susu.algebra.methods.IMathMethodPS;
import ru.susu.algebra.partition.Partition;
import ru.susu.algebra.properties.IPropertySource;
import ru.susu.algebra.wrapper.number.BigIntegerFraction;

/**
 * @author akargapolov
 * @since: 15.07.2010
 */
public class AlternatingGroupCentralUnitsSearcher implements IMathMethodPS<ITexDocument>
{
	private static final Logger _log = Logger.getLogger("searcher");

	@Override
	public synchronized ITexDocument run(IPropertySource ps) throws Exception
	{
		_log.info("Start for " + CentralUnitsOptions.getNumber(ps));
		Date start = new Date();

		ITexDocument document = new TexDocument();
		//стили и настройки
		document.setHead(new HeadAnTexGenerator().run(ps)).newLine();

		//название
		document.addElement(new TitleTexGenerator().run(ps)).newLine();

		//локальный случай
		document.addElement(new LocalCaseTexGenerator().run(ps)).newLine();

		_log.info("End " + CentralUnitsOptions.getNumber(ps) + " - " + (new Date().getTime() - start.getTime()) + "ms");

		/*
		_log.info("start search central units of alternating group A_" + _number);
		_specialRows = AlternatingCharTableUtil.listSpecialRows(_number);
		_specialNotIntegerRows = AlternatingCharTableUtil.listSpecialRowsNotIntegerCharacter(_number);
		_specialRowsIndex = AlternatingCharTableUtil.indexRowNumbers(_specialRows);




		/*if (_specialRows.size() > 1)
			return null;
		_fields = AlternatingCharTableUtil.field4SprecialRows(_specialRows);
		_log.info("Основные единицы квадратичных полей: " + _fields.keySet());

		if (_specialRows.isEmpty())
		{
			return null;
		}

		_zxs = AlternatingCharTableUtil.listZXs(_specialRows);
		_log.info("Z(X): " + _zxs);

		_exponents = ExponentUtils.evaluateExponents(_specialRows);
		_log.info("Экспоненты: " + _exponents);


		//sucks
		/*
		_factorial = NumberUtilities.getFactorial(_number).divide(BigInteger.valueOf(2));
		partitions1 = AlternatingCharTableUtil.getAscendingDegreeFirstAssociatedPartitions(_number);
		partitions2 = CollectionsUtil.sort(new PartitionsCacheConstructor().getAscendingPartitions(_number, new AlternatingPartitionsFilter()),
				true, new Partition4SpecialColumnLast());
		Collection<QuadraticFieldGeneralForm> _units = Collections2.transform(_fields.keySet(), new Function<QuadraticField, QuadraticFieldGeneralForm>(){
			@Override
			public QuadraticFieldGeneralForm apply(QuadraticField from)
			{
				return from.getGeneralUnit();
			}});
		UNITS = new QuadraticFieldGeneralForm[_units.size()];
		_units.toArray(UNITS);

		pows = new BigInteger[_exponents.size()];
		_exponents.toArray(pows);

		rows = getRows();
		e = new Integer[rows.length];
		Arrays.fill(e, 1);

		for (int i = 0; i < pows.length; i++)
		{
			for (Pair<Integer, Integer> pair : NumberUtilities.factorization(pows[i]))
			{
				for (int j = 0;j<pair.getValue();j++)
				{
					pows[i] = pows[i].divide(BigInteger.valueOf(pair.getKey()));
					boolean checkResult = check(System.out);
					//_log.info(Arrays.toString(pows) + " - " + checkResult);
					if (!checkResult)
					{
						pows[i] = pows[i].multiply(BigInteger.valueOf(pair.getKey()));
						break;
					}
				}
			}
		}

		_log.info("Степени для A_" + _number +  ": " + Arrays.toString(pows));
*/
		return document;
	}

	private static final BigInteger TWO = BigInteger.valueOf(2);

	private static List<Partition> partitions1;
	private static List<Partition> partitions2;
	private static AlternatingGroupCharTableConstructor constructor = AlternatingGroupCharTableConstructor.getInstance();
	private static Integer[] rows;
	private static QuadraticFieldGeneralForm[] UNITS = {new QuadraticFieldGeneralForm(1, 1, 13), new QuadraticFieldGeneralForm(19, 8, 33), new QuadraticFieldGeneralForm(0, 1,5)};
	//private static BigInteger[] POWS = {BigInteger.valueOf(3360), BigInteger.valueOf(840), BigInteger.valueOf(2016)};
	private static BigInteger _factorial;
	//по возрастанию степени

	private static Integer[] e = {1, 1, 1};
	private static BigInteger[] pows = new BigInteger[3];

	private boolean check(PrintStream writer) throws Exception
	{
	//	writer.println("Start checking");
		//writer.println("e[0] = " + e[0] + " e[1] = " + e[1] + " e[2] = " + e[2]);
		//writer.println("k[0] = " + pows[0] + " k[1] = " + pows[1] + " k[2] = " + pows[2]);

		for (int j = partitions2.size() - 1; j >= 0; j--)
		{
			//writer.println(j + " partition: " + partitions2.get(j) + " [" + partitions2.get(j).getOrderNumber() + "]");
			BigIntegerFraction fraction = getStaticPart(writer, j);
			//writer.println("Static part: " + fraction);
			if (!AlternatingCharTableUtil.isSpecialColumn(partitions2.get(j)))
			{
				for (int i = 0; i < rows.length; i++)
				{
					BigIntegerFraction unitPart = getUnitPart(writer, i, j, false);
				//	writer.print(i + " unit part: " + unitPart + " = ");
					getUnitPart(writer, i, j, true);
					fraction = fraction.add(unitPart);
					//writer.println();
				}
				//writer.println("Sum: " + fraction);
				if (!fraction.getDenominator().equals(BigInteger.ONE))
				{
					return false;
				}
			}
			else
			{
				BigIntegerFraction sum[] = {fraction, fraction};
				for (int i = 0; i < rows.length; i++)
				{
					for (int k = 0;k< 2;k++)
					{
						BigIntegerFraction tmp = getUnitColumnPart(writer, i, j, k, false);
				//		writer.println(tmp);
						sum[k] = sum[k].add(tmp);
					}
				}
				//writer.println("Sum[0]: " + sum[0]);
				//writer.println("Sum[1]: " + sum[1]);
				if (!sum[0].getDenominator().equals(BigInteger.ONE) || !sum[1].getDenominator().equals(BigInteger.ONE))
				{
					return false;
				}
			}
		}
		return true;
	}

	private boolean isAllZero(Matrix matrix, int col)
	{
		for (int i = 0;i<matrix.getRows();i++)
			if (!(matrix.getValue(i, col) instanceof BigInteger) || !BigInteger.ZERO.equals(matrix.getValue(i, col)))
			{
				return false;
			}
		return true;
	}

	private BigIntegerFraction getUnitColumnPart(PrintStream writer, int unit, int partition2, int col, boolean debug) throws Exception
	{
		Partition p1 = partitions1.get(rows[unit]);
		Partition p2 = partitions2.get(partition2);
		Matrix matrix = constructor.getValue(p1, p2);
		if (isAllZero(matrix, col))
		{
			return BigIntegerFraction.ZERO;
		}
		QuadraticInteger gunit = UNITS[unit].modPow(pows[unit], _factorial);
		QuadraticIntegerExtractor extractor = QuadraticIntegerExtractor.getInstance();
		QuadraticInteger value1 =extractor.apply(matrix.getValue(0, col));
		QuadraticInteger firstPart = gunit.multiply(value1);
		QuadraticInteger value2 =extractor.apply(matrix.getValue(1, col));
		QuadraticInteger secondPart = getConjugatePow(UNITS[unit], pows[unit]).multiply(value2);
		QuadraticInteger summ = firstPart.add(secondPart);
		if (!summ.getB().equals(BigInteger.ZERO))
		{
			throw new RuntimeException("I can't process not integer");
		}
		BigInteger tmp = summ.getA().multiply(BigInteger.valueOf(e[unit]));
		tmp = tmp.multiply(AlternatingCharTableUtil.calcAlternatingDegree(p1));//mul deg
		return new BigIntegerFraction(tmp, _factorial);
	}

	private QuadraticInteger getConjugatePow(QuadraticFieldGeneralForm gunit, BigInteger pow) throws Exception
	{
		QuadraticInteger tmp = gunit.modPow(pow, _factorial);
		return tmp.getConjugateNumber();
	}

	private BigIntegerFraction getUnitPart(PrintStream writer, int unit, int j, boolean debug) throws Exception
	{
		BigIntegerFraction result = getOnePart(rows[unit], j);
		QuadraticInteger gunit = UNITS[unit].modPow(pows[unit], _factorial);
		//writer.println("unit^" + pows[unit] + "(mod|An|) = " + gunit);
		BigInteger numerator = gunit.getA().multiply(TWO).add(gunit.getB()).multiply(BigInteger.valueOf(e[unit]));
		if (debug)
		{
			//writer.print(numerator + " * " + result);
		}
		result = result.multiply(new BigIntegerFraction(numerator));
		return result;
	}

	private BigIntegerFraction getStaticPart(PrintStream writer, int j) throws Exception
	{
		BigIntegerFraction fraction = new BigIntegerFraction(0);
		for (int i = 0;i < partitions1.size();i++)
		{
			Partition p1 = partitions1.get(i);
			Partition p2 = partitions2.get(j);
			Matrix value = constructor.getValue(p1, p2);
			if (value.getRows() > 1)
			{
				continue;
			}
			BigIntegerFraction onePart = getOnePart(i, j);
			//writer.print(" " + onePart);
			fraction = fraction.add(onePart);
		}
		//writer.println();
		return fraction;
	}

	private BigIntegerFraction getOnePart(int i, int j) throws Exception
	{
		BigIntegerFraction result;
		Partition p1 = partitions1.get(i);
		Partition p2 = partitions2.get(j);
		Matrix value = constructor.getValue(p1, p2);
		/*
		if (value.getCols() > 1)
		{
			throw new RuntimeException("I can't process special column");
		}*/
		if (!(value.getValue(0, 0) instanceof BigInteger))
		{
			throw new RuntimeException("I can't process not BigIntegers");
		}
		BigInteger tmp = (BigInteger)value.getValue(0, 0);//p1xp2
		tmp = tmp.multiply(AlternatingCharTableUtil.calcAlternatingDegree(p1));//mul deg
		result =new BigIntegerFraction(tmp, _factorial);
		return result;
	}

	private Integer[] getRows()
	{
		/*
		List<Integer> rows = new ArrayList<Integer>();
		for (Map.Entry<QuadraticField, Partition> entry : _fields.entrySet())
		{
			rows.add(partitions1.indexOf(entry.getValue()));
		}
		return rows.toArray(new Integer[0]);*/
		return null;
	}
}
